/// @author: pengboboer
/// @createDate: 2022/1/6
import 'package:flutter/services.dart';

///价格输入框和数量输入框的限制
class PriceOrCountFilteringTextInputFormatter extends TextInputFormatter {
  static const String POINTER = ".";
  static const String ZERO = "00";

  PriceOrCountFilteringTextInputFormatter(
    this.filterPattern, {
    required this.allow,
    this.replacementString = '',
  })  : assert(filterPattern != null),
        assert(allow != null),
        assert(replacementString != null);

  /// Creates a formatter that only allows characters matching a pattern.
  ///
  /// The [filterPattern] and [replacementString] arguments
  /// must not be null.
  PriceOrCountFilteringTextInputFormatter.allow(
    Pattern filterPattern, {
    String replacementString = '',
  }) : this(filterPattern, allow: true, replacementString: replacementString);

  /// Creates a formatter that blocks characters matching a pattern.
  ///
  /// The [filterPattern] and [replacementString] arguments
  /// must not be null.
  PriceOrCountFilteringTextInputFormatter.deny(
    Pattern filterPattern, {
    String replacementString = '',
  }) : this(filterPattern, allow: false, replacementString: replacementString);

  final Pattern filterPattern;

  final bool allow;

  final String replacementString;

  int scale = 2;

  @override
  TextEditingValue formatEditUpdate(
    TextEditingValue oldValue, // unused.
    TextEditingValue newValue,
  ) {
    final _TextEditingValueAccumulator formatState =
        _TextEditingValueAccumulator(newValue);
    assert(!formatState.debugFinalized);

    ///不能以"."和"00"开头
    if (newValue.text.startsWith(POINTER) || newValue.text.startsWith(ZERO)) {
      return oldValue;
    }

    /// 开头只能输入一个0
    if (oldValue.text.trim() == "0") {
      if (newValue.text.trim() != "0." && newValue.text.length > 1) {
        return oldValue;
      }
    }

    /// 防止先输入数字后点击到前面输入0
    if (oldValue.text.trim().isNotEmpty) {
      if (newValue.text.trim() != "0." &&
          !oldValue.text.trim().startsWith("0.") &&
          newValue.text.trim().startsWith("0")) {
        return oldValue;
      }
    }

    ///包含小数点的情况
    if (newValue.text.contains(POINTER)) {
      ///包含多个小数
      if (newValue.text.indexOf(POINTER) !=
          newValue.text.lastIndexOf(POINTER)) {
        return oldValue;
      }
      String input = newValue.text;
      int index = input.indexOf(POINTER);

      ///小数点后位数
      int lengthAfterPointer = input.substring(index, input.length).length - 1;

      ///小数位大于精度
      if (lengthAfterPointer > scale) {
        return oldValue;
      }
    }

    final Iterable<Match> matches = filterPattern.allMatches(newValue.text);
    Match? previousMatch;
    for (final Match match in matches) {
      assert(match.end >= match.start);
      // Compute the non-match region between this `Match` and the previous
      // `Match`. Depending on the value of `allow`, either the match region or
      // the non-match region is the banned pattern.
      //
      // The non-matching region.
      _processRegion(allow, previousMatch?.end ?? 0, match.start, formatState);
      assert(!formatState.debugFinalized);
      // The matched region.
      _processRegion(!allow, match.start, match.end, formatState);
      assert(!formatState.debugFinalized);

      previousMatch = match;
    }

    // Handle the last non-matching region between the last match region and the
    // end of the text.
    _processRegion(
        allow, previousMatch?.end ?? 0, newValue.text.length, formatState);
    assert(!formatState.debugFinalized);
    return formatState.finalize();
  }

  void _processRegion(bool isBannedRegion, int regionStart, int regionEnd,
      _TextEditingValueAccumulator state) {
    final String replacementString = isBannedRegion
        ? (regionStart == regionEnd ? '' : this.replacementString)
        : state.inputValue.text.substring(regionStart, regionEnd);

    state.stringBuffer.write(replacementString);

    if (replacementString.length == regionEnd - regionStart) {
      // We don't have to adjust the indices if the replaced string and the
      // replacement string have the same length.
      return;
    }

    int adjustIndex(int originalIndex) {
      // The length added by adding the replacementString.
      final int replacedLength =
          originalIndex <= regionStart && originalIndex < regionEnd
              ? 0
              : replacementString.length;
      // The length removed by removing the replacementRange.
      final int removedLength =
          originalIndex.clamp(regionStart, regionEnd) - regionStart;
      return replacedLength - removedLength;
    }

    state.selection?.base += adjustIndex(state.inputValue.selection.baseOffset);
    state.selection?.extent +=
        adjustIndex(state.inputValue.selection.extentOffset);
    state.composingRegion?.base +=
        adjustIndex(state.inputValue.composing.start);
    state.composingRegion?.extent +=
        adjustIndex(state.inputValue.composing.end);
  }

  /// A [TextInputFormatter] that forces input to be a single line.
  static final TextInputFormatter singleLineFormatter =
      FilteringTextInputFormatter.deny('\n');

  /// A [TextInputFormatter] that takes in digits `[0-9]` only.
  static final TextInputFormatter digitsOnly =
      FilteringTextInputFormatter.allow(RegExp(r'[0-9]'));
}

class _TextEditingValueAccumulator {
  _TextEditingValueAccumulator(this.inputValue)
      : selection = _MutableTextRange.fromTextSelection(inputValue.selection),
        composingRegion =
            _MutableTextRange.fromComposingRange(inputValue.composing);

  // The original string that was sent to the [FilteringTextInputFormatter] as
  // input.
  final TextEditingValue inputValue;

  /// The [StringBuffer] that contains the string which has already been
  /// formatted.
  ///
  /// In a [FilteringTextInputFormatter], typically the replacement string,
  /// instead of the original string within the given range, is written to this
  /// [StringBuffer].
  final StringBuffer stringBuffer = StringBuffer();

  /// The updated selection, as well as the original selection from the input
  /// [TextEditingValue] of the [FilteringTextInputFormatter].
  ///
  /// This parameter will be null if the input [TextEditingValue.selection] is
  /// invalid.
  final _MutableTextRange? selection;

  /// The updated composing region, as well as the original composing region
  /// from the input [TextEditingValue] of the [FilteringTextInputFormatter].
  ///
  /// This parameter will be null if the input [TextEditingValue.composing] is
  /// invalid or collapsed.
  final _MutableTextRange? composingRegion;

  // Whether this state object has reached its end-of-life.
  bool debugFinalized = false;

  TextEditingValue finalize() {
    debugFinalized = true;
    final _MutableTextRange? selection = this.selection;
    final _MutableTextRange? composingRegion = this.composingRegion;
    return TextEditingValue(
      text: stringBuffer.toString(),
      composing: composingRegion == null ||
              composingRegion.base == composingRegion.extent
          ? TextRange.empty
          : TextRange(start: composingRegion.base, end: composingRegion.extent),
      selection: selection == null
          ? const TextSelection.collapsed(offset: -1)
          : TextSelection(
              baseOffset: selection.base,
              extentOffset: selection.extent,
              // Try to preserve the selection affinity and isDirectional. This
              // may not make sense if the selection has changed.
              affinity: inputValue.selection.affinity,
              isDirectional: inputValue.selection.isDirectional,
            ),
    );
  }
}

class _MutableTextRange {
  _MutableTextRange(this.base, this.extent);

  static _MutableTextRange? fromComposingRange(TextRange range) {
    return range.isValid && !range.isCollapsed
        ? _MutableTextRange(range.start, range.end)
        : null;
  }

  static _MutableTextRange? fromTextSelection(TextSelection selection) {
    return selection.isValid
        ? _MutableTextRange(selection.baseOffset, selection.extentOffset)
        : null;
  }

  /// The start index of the range, inclusive.
  ///
  /// The value of [base] should always be greater than or equal to 0, and can
  /// be larger than, smaller than, or equal to [extent].
  int base;

  /// The end index of the range, exclusive.
  ///
  /// The value of [extent] should always be greater than or equal to 0, and can
  /// be larger than, smaller than, or equal to [base].
  int extent;
}
